﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace ExpressionMapper
{
    using MappingExpressionGenerator = Func<Expression, Type, Expression, ParameterExpression, Expression>;

    /// <summary>
    /// Returns generator for Dictionary to Dictionary mappings.
    /// </summary>
    public static partial class Generators
    {
        public static MappingExpressionGenerator DictionaryToDictionary()
        {
            return (fromExpr, to, customMapping, referenceDictionary) =>
                   (ImplementsDictionary(fromExpr.Type) && IsDictionary(to))
                       ? customMapping ?? GenerateDictionaryConversionExpression(fromExpr, to, referenceDictionary)
                       : null;
        }

        private static Expression GenerateDictionaryConversionExpression(Expression fromExpr,
                                                                            Type to, ParameterExpression referenceDictionary)
        {
            Debug.WriteLine("\tDictionaryToDictionary ({0}): {1} to {2}", fromExpr.NodeType, fromExpr.Type.Name,
                            to.Name);


            if (IsSimpleDictionary(fromExpr.Type))
            {
                MethodInfo convertObjectDictionary = typeof(Generators)
               .GetMethod("ConvertFromObjectDictionary", BindingFlags.Static | BindingFlags.Public);
                fromExpr = Expression.Call(convertObjectDictionary, fromExpr);
            }

            if (IsStringDictionary(fromExpr.Type))
            {
                MethodInfo convertstringDictionary = typeof(Generators)
               .GetMethod("ConvertFromStringDictionary", BindingFlags.Static | BindingFlags.Public);
                fromExpr = Expression.Call(convertstringDictionary, fromExpr);
            }
            if (IsNameValueCollection(fromExpr.Type))
            {
                MethodInfo convertstringDictionary = typeof(Generators)
               .GetMethod("ConvertFromNameValueCollection", BindingFlags.Static | BindingFlags.Public);
                fromExpr = Expression.Call(convertstringDictionary, fromExpr);
            }

            Type fromKeyType = GetDictionaryKeyType(fromExpr.Type);
            Type fromElementType = GetDictionaryElementType(fromExpr.Type);

            MethodInfo convertAllMethod;
            if (IsStringDictionary(to) || IsNameValueCollection(to))
            {
                convertAllMethod = typeof(Generators)
                                   .GetMethod("Convert" + to.GetGenericTypeName(), BindingFlags.Static | BindingFlags.Public)
                                   .MakeGenericMethod(fromKeyType, fromElementType);
            }
            else
            {
                Type toKeyType = GetDictionaryKeyType(to);
                Type toElementType = GetDictionaryElementType(to);
                string methodName = "Convert" + to.GetGenericTypeName();
                if (IsObjectSortedList(to))
                {
                    methodName = "ConvertObjectSortedList";
                }
                convertAllMethod = typeof(Generators)
                    .GetMethod(methodName, BindingFlags.Static | BindingFlags.Public)
                    .MakeGenericMethod(fromKeyType, fromElementType, toKeyType, toElementType);

            }
            return Expression.Call(convertAllMethod, fromExpr, referenceDictionary);
        }

        private static bool ImplementsDictionary(Type t)
        {
            return (t.IsGenericType && typeof(IDictionary<,>).IsAssignableFrom(t.GetGenericTypeDefinition())) ||
                   typeof(IDictionary).IsAssignableFrom(t) ||
                   t == typeof(StringDictionary) ||
                   t == typeof(NameValueCollection) ||
                   t == typeof(SortedList)
                   ||
                   (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(SortedList<,>));
        }

        #region IsDictionary

        private static bool IsDictionary(Type t)
        {
            if (IsSimpleDictionary(t) ||
                IsGenericDictionary(t) ||
                IsConcurrentDictionary(t) ||
                IsStringDictionary(t) ||
                IsOrderedDictionary(t) ||
                IsListDictionary(t) ||
                IsHybridDictionary(t) ||
                IsNameValueCollection(t) ||
                IsHashtable(t) ||
                IsGenericSortedList(t) ||
                IsObjectSortedList(t))
                return true;


            return false;
        }

        private static bool IsGenericSortedList(Type t)
        {
            return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(SortedList<,>);
        }

        private static bool IsObjectSortedList(Type t)
        {
            return t == typeof(SortedList);
        }

        private static bool IsSimpleDictionary(Type t)
        {
            return !t.IsGenericType && typeof(IDictionary).IsAssignableFrom(t);
        }

        private static bool IsGenericDictionary(Type t)
        {
            return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>)
                //|| typeof(Dictionary<,>).IsAssignableFrom(t)
                ;
        }

        private static bool IsConcurrentDictionary(Type t)
        {
            return (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ConcurrentDictionary<,>))
                // || typeof(ConcurrentDictionary<,>).IsAssignableFrom(t)
               ;
        }

        private static bool IsStringDictionary(Type t)
        {
            return t == typeof(StringDictionary) //|| typeof(StringDictionary).IsAssignableFrom(t)
                ;
        }

        private static bool IsOrderedDictionary(Type t)
        {
            return t == typeof(OrderedDictionary) //|| typeof(OrderedDictionary).IsAssignableFrom(t)
                ;
        }

        private static bool IsListDictionary(Type t)
        {
            return t == typeof(ListDictionary) //|| typeof(ListDictionary).IsAssignableFrom(t)
                ;
        }

        private static bool IsHybridDictionary(Type t)
        {
            return t == typeof(HybridDictionary)
                //|| typeof(HybridDictionary).IsAssignableFrom(t)
                ;
        }

        private static bool IsHashtable(Type t)
        {
            return t == typeof(Hashtable);
        }

        private static bool IsNameValueCollection(Type t)
        {
            return t == typeof(NameValueCollection) //|| typeof(NameValueCollection).IsAssignableFrom(t)
                ;
        }

        #endregion IsDictionary

        private static Type GetDictionaryElementType(Type t)
        {
            if (t.IsGenericType)
                return t.GetGenericArguments()[1];
            if (IsStringDictionary(t) || IsNameValueCollection(t))
                return typeof(string);
            return typeof(object);
        }

        private static Type GetDictionaryKeyType(Type t)
        {
            if (t.IsGenericType)
                return t.GetGenericArguments()[0];
            if (IsStringDictionary(t) || IsNameValueCollection(t))
                return typeof(string);
            return typeof(object);
        }

        #region ConvertFrom ..

        public static Dictionary<object,object> ConvertFromObjectDictionary(IDictionary input)
        {
            if (input == null)
            {
                return null;
            }
            Dictionary<object, object> result = new Dictionary<object, object>();

            foreach (var key in input.Keys)
            {
                result.Add(key, input[key]);
            }

            return result;
        }

        public static Dictionary<string, string> ConvertFromNameValueCollection(NameValueCollection input)
        {
            if (input == null)
            {
                return null;
            }
            Dictionary<string, string> result = new Dictionary<string, string>();

            foreach (string key in input.Keys)
            {
                result.Add(key, input[key]);
            }

            return result;
        }

        public static Dictionary<string, string> ConvertFromStringDictionary(StringDictionary input)
        {
            if (input == null)
            {
                return null;
            }
            Dictionary<string, string> result = new Dictionary<string, string>();

            foreach (string key in input.Keys)
            {
                result.Add(key, input[key]);
            }

            return result;
        }

        #endregion

        public static Dictionary<TOutputKey, TOutput> ConvertDictionary<TInputKey, TInput, TOutputKey, TOutput>
            (IDictionary<TInputKey, TInput> input, ReferenceDictionary parentDictionary)
        {
            if (input == null)
            {
                return null;
            }
            
            return input.ToDictionary(item => Mapper.Map<TInputKey, TOutputKey>(item.Key, parentDictionary)
                                    , item => Mapper.Map<TInput, TOutput>(item.Value, parentDictionary));
        }

        public static ConcurrentDictionary<TOutputKey, TOutput> ConvertConcurrentDictionary<TInputKey, TInput, TOutputKey, TOutput>
            (IDictionary<TInputKey, TInput> input, ReferenceDictionary parentDictionary)
        {

            if (input == null)
            {
                return null;
            }

            return new ConcurrentDictionary<TOutputKey, TOutput>(
                ConvertDictionary<TInputKey, TInput, TOutputKey, TOutput>(input, parentDictionary));
        }

        public static OrderedDictionary ConvertOrderedDictionary<TInputKey, TInput, TOutputKey, TOutput>(
            IDictionary<TInputKey, TInput> input, ReferenceDictionary parentDictionary)
        {
            if (input == null)
            {
                return null;
            }
            OrderedDictionary result = new OrderedDictionary();
            
            foreach (var item in input)
            {
                result.Add(Mapper.Map<TInputKey, TOutputKey>(item.Key, parentDictionary), Mapper.Map<TInput, TOutput>(item.Value, parentDictionary));
            }

            return result;
        }

        public static ListDictionary ConvertListDictionary<TInputKey, TInput, TOutputKey, TOutput>(
            IDictionary<TInputKey, TInput> input, ReferenceDictionary parentDictionary)
        {
            if (input == null)
            {
                return null;
            }
            ListDictionary result = new ListDictionary();
            
            foreach (var item in input)
            {
                result.Add(Mapper.Map<TInputKey, TOutputKey>(item.Key, parentDictionary), Mapper.Map<TInput, TOutput>(item.Value, parentDictionary));
            }

            return result;
        }

        public static HybridDictionary ConvertHybridDictionary<TInputKey, TInput, TOutputKey, TOutput>(
            IDictionary<TInputKey, TInput> input, ReferenceDictionary parentDictionary)
        {
            if (input == null)
            {
                return null;
            }

            HybridDictionary result = new HybridDictionary();
            
            foreach (var item in input)
            {
                result.Add(Mapper.Map<TInputKey, TOutputKey>(item.Key, parentDictionary), Mapper.Map<TInput, TOutput>(item.Value, parentDictionary));
            }

            return result;
        }

        public static Hashtable ConvertHashtable<TInputKey, TInput, TOutputKey, TOutput>(
            IDictionary<TInputKey, TInput> input, ReferenceDictionary parentDictionary)
        {
            if (input == null)
            {
                return null;
            }

            Hashtable result = new Hashtable();
            
            foreach (var item in input)
            {
                result.Add(Mapper.Map<TInputKey, TOutputKey>(item.Key, parentDictionary), Mapper.Map<TInput, TOutput>(item.Value, parentDictionary));
            }

            return result;
        }

        public static SortedList<TOutputKey, TOutput> ConvertSortedList<TInputKey, TInput, TOutputKey, TOutput>
                    (IDictionary<TInputKey, TInput> input, ReferenceDictionary parentDictionary)
        {
            if (input == null)
            {
                return null;
            }

            SortedList<TOutputKey, TOutput> result = new SortedList<TOutputKey, TOutput>();
            
            foreach (var item in input)
            {
                result.Add(Mapper.Map<TInputKey, TOutputKey>(item.Key, parentDictionary), Mapper.Map<TInput, TOutput>(item.Value, parentDictionary));
            }

            return result;
        }

        public static SortedList ConvertObjectSortedList<TInputKey, TInput, TOutputKey, TOutput>
            (IDictionary<TInputKey, TInput> input, ReferenceDictionary parentDictionary)
        {
            if (input == null)
            {
                return null;
            }

            SortedList result = new SortedList();
            
            foreach (var item in input)
            {
                result.Add(Mapper.Map<TInputKey, TOutputKey>(item.Key, parentDictionary), Mapper.Map<TInput, TOutput>(item.Value, parentDictionary));
            }

            return result;
        }

        public static StringDictionary ConvertStringDictionary<TInputKey, TInput>
            (IDictionary<TInputKey, TInput> input, ReferenceDictionary parentDictionary)
        {
            if (input == null)
            {
                return null;
            }
            StringDictionary result = new StringDictionary();
            
            foreach (var item in input)
            {
                result.Add(Mapper.Map<TInputKey, string>(item.Key, parentDictionary), Mapper.Map<TInput, string>(item.Value, parentDictionary));
            }

            return result;
        }

        public static NameValueCollection ConvertNameValueCollection<TInputKey, TInput>(
           IDictionary<TInputKey, TInput> input, ReferenceDictionary parentDictionary)
        {
            if (input == null)
            {
                return null;
            }
            NameValueCollection result = new NameValueCollection();
            
            foreach (var item in input)
            {
                result.Add(Mapper.Map<TInputKey, string>(item.Key, parentDictionary), Mapper.Map<TInput, string>(item.Value, parentDictionary));
            }

            return result;
        }
    }
}